package pers.shmily.sdk.redis.component;

import io.lettuce.core.RedisException;
import org.springframework.data.redis.connection.RedisListCommands;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author shmily
 * @create 2024/6/4
 */
@Component
public class RedisUtil {

    /*
     * Redis命令参考：
     * Reference: http://doc.redisfans.com/
     * Reference: https://redis.com.cn/commands.html
     */

    private final RedisTemplate<String, Object> redisTemplate;
    private final ValueOperations<String, Object> opsForValue;
    private final HashOperations<String, String, Object> opsForHash;
    private final ListOperations<String, Object> opsForList;
    private final SetOperations<String, Object> opsForSet;
    private final ZSetOperations<String, Object> opsForZSet;

    public RedisUtil(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
        this.opsForValue = redisTemplate.opsForValue();
        this.opsForHash = redisTemplate.opsForHash();
        this.opsForList = redisTemplate.opsForList();
        this.opsForSet = redisTemplate.opsForSet();
        this.opsForZSet = redisTemplate.opsForZSet();
    }

    /*------------------------------ Key ------------------------------*/

    /**
     * 删除给定的一个或多个 key，不存在的 key 会被忽略
     *
     * @param keys keys
     * @return 被删除 key 的数量
     * @since redis.version >= 1.0.0
     */
    public int del(String... keys) {
        Long delete = redisTemplate.delete(Arrays.asList(keys));
        return delete == null ? 0 : delete.intValue();
    }

    /**
     * 检查给定的一个或多个 key 是否存在
     *
     * @param keys key
     * @return 返回待检查 key 中存在的 key 的个数，如果相同的 key 在参数列表中出现了多次，它会被计算多次。
     * @since redis.version >= 3.0.3
     */
    public int exists(String... keys) {
        Long counted = redisTemplate.countExistingKeys(Arrays.asList(keys));
        return counted == null ? 0 : counted.intValue();
    }

    /**
     * 设置 key 的过期时间，设置的时间过期后，key 会被自动删除。设置已经有过期时间的key，key的过期时间将被更新为新值
     *
     * @param key     key
     * @param timeout 过期时间
     * @param unit    时间单位
     * @return 如果key不存在，则返回false
     * @since redis.version >= 2.1.3
     */
    public boolean expire(String key, long timeout, TimeUnit unit) {
        return Boolean.TRUE.equals(redisTemplate.expire(key, timeout, unit));
    }

    /**
     * 查找所有匹配给定模式 pattern 的 key
     *
     * @param pattern pattern 使用 \ 转义你想匹配的特殊字符。
     * @return 返回匹配模式 pattern 的 key 的列表
     * @since redis.version >= 1.0.0
     */
    public Set<String> keys(String pattern) {
        return redisTemplate.keys(pattern);
    }

    /**
     * 将当前数据库的 key 移动到选定的数据库 db 当中
     *
     * @param key     key
     * @param dbIndex 数据库索引
     * @return true:移动成功 false:移动失败
     * @since redis.version >= 1.0.0
     */
    public boolean move(String key, int dbIndex) {
        return Boolean.TRUE.equals(redisTemplate.move(key, dbIndex));
    }

    /**
     * 删除给定 key 的过期时间，使得 key 永不过期。
     *
     * @param key key
     * @return true: 当过期时间移除成功时   false: 如果 key 不存在或 key 没有设置过期时间
     * @since redis.version >= 2.2.0
     */
    public boolean persist(String key) {
        return Boolean.TRUE.equals(redisTemplate.persist(key));
    }

    /**
     * 返回 key 的剩余过期时间，默认以秒为单位
     *
     * @param key key
     * @return Redis 2.6 之前的版本如果 key 不存在或者 key 没有关联超时时间则返回 -1;
     * Redis 2.8 起：key 不存在返回 -2 key 存在但是没有关联超时时间返回 -1
     * @since redis.version >= 1.0.0
     */
    public int ttl(String key) {
        Long expire = redisTemplate.getExpire(key);
        return expire == null ? 0 : expire.intValue();
    }

    /**
     * 以指定单位返回 key 的剩余过期时间
     *
     * @param key  key
     * @param unit 时间单位
     * @return Redis 2.6 之前的版本如果 key 不存在或者 key 没有关联超时时间则返回 -1;
     * Redis 2.8 起：key 不存在返回 -2 key 存在但是没有关联超时时间返回 -1
     * @since redis.version >= 1.0.0
     */
    public int ttl(String key, TimeUnit unit) {
        Long expire = redisTemplate.getExpire(key, unit);
        return expire == null ? 0 : expire.intValue();
    }

    /**
     * 修改 key 的名字为 newKey。若 key 不存在返回错误，若 newKey 已存在，则newKey的值将被覆盖。
     * redis.version <= 3.2.0: 源 key 和目的 key 相同时返回错误。
     *
     * @param key    key
     * @param newKey newKey
     * @since redis.version >= 1.0.0
     */
    public void rename(String key, String newKey) {
        redisTemplate.rename(key, newKey);
    }

    /**
     * 在新的 key 不存在时修改 key 的名称。
     *
     * @param key    key
     * @param newKey newKey
     * @return 若 key 不存在抛出异常。<= 3.2.0: 源 key 和目的 key 相同抛出异常；若 newKey 已存在返回false；成功修改返回true;
     * @since redis.version >= 1.0.0
     */
    public boolean renamenx(String key, String newKey) {
        return Boolean.TRUE.equals(redisTemplate.renameIfAbsent(key, newKey));
    }

    /**
     * 以字符串的形式返回存储在 key 中的值的类型。
     *
     * @param key key
     * @return 返回存储在 key 中的值的类型。key 不存在时返回 none。
     */
    public String type(String key) {
        return Objects.requireNonNull(redisTemplate.type(key)).code();
    }

    /*------------------------------ String ------------------------------*/

    /**
     * 将键 key 设定为指定的“字符串”值。
     * 如果 key 已经保存了一个值，那么这个操作会直接覆盖原来的值，并且忽略原始类型。
     * 当 set 命令执行成功之后，之前设置的过期时间都将失效
     *
     * @param key   key
     * @param value value
     * @since redis.version >= 1.0.0
     */
    public void set(String key, Object value) {
        opsForValue.set(key, value);
    }

    /**
     * 将键 key 的值设置为 value，并将键 key 设置生存时间，如果键 key 已经存在，那么将覆盖原有的值
     *
     * @param key     key
     * @param value   value
     * @param timeout 生存时间
     * @param unit    时间单位
     * @since redis.version >= 2.0.0
     */
    public void setex(String key, Object value, long timeout, TimeUnit unit) {
        opsForValue.set(key, value, timeout, unit);
    }

    /**
     * 当且仅当 key 不存在时，将 key 的值设为 value。
     * 若给定的 key 已经存在，则 SETNX 不做任何动作
     *
     * @param key   key
     * @param value value
     * @return 设置成功返回true，设置失败返回false
     * @since redis.version >= 1.0.0
     */
    public boolean setnx(String key, Object value) {
        Boolean b = opsForValue.setIfAbsent(key, value);
        return Boolean.TRUE.equals(b);
    }

    /**
     * 获取 key 所关联的值。默认以String类型返回
     *
     * @param key key
     * @return 返回 key 所关联的字符串值。 如果 key 不存在那么返回 null
     * @since redis.version >= 1.0.0
     */
    public String get(String key) {
        return get(key, String.class);
    }

    /**
     * 获取 key 所关联的值。并以指定类型返回
     *
     * @param key key
     * @return 返回 key 所关联的值。并反序列化为指定类型的对象，如果 key 不存在那么返回 null
     * @since redis.version >= 1.0.0
     */
    public <T> T get(String key, Class<T> tClass) {
        Object o = opsForValue.get(key);
        return tClass.cast(o);
    }

    /**
     * 获取并删除，相当于 get() 和 del() 的组合，不同之处是此方法是原子性的
     *
     * @param key key
     * @return 返回 key 所关联的字符串值。 如果 key 不存在那么返回 null
     * @since redis.version >= 1.0.0
     */
    public String getAndDel(String key) {
        return getAndDel(key, String.class);
    }

    /**
     * 获取并删除，相当于 get() 和 del() 的组合，不同之处是此方法是原子性的
     *
     * @param key key
     * @return 返回 key 所值，并反序列化为指定类型的对象。 如果 key 不存在那么返回 null
     * @since redis.version >= 1.0.0
     */
    public <T> T getAndDel(String key, Class<T> tClass) {
        Object o = opsForValue.getAndDelete(key);
        return tClass.cast(o);
    }

    /**
     * 同时设置一个或多个 key-value 对，
     * MSET 是一个原子性(atomic)操作，所有给定 key 都会在同一时间内被设置，某些给定 key 被更新而另一些给定 key 没有改变的情况，不可能发生。
     *
     * @param entries key-value对集合
     * @since redis.version >= 1.0.1
     */
    public void mset(Map<String, Object> entries) {
        opsForValue.multiSet(entries);
    }

    /**
     * 当且仅当所有给定键都不存在时，为所有给定键设置值。
     * 即使只有一个给定键已经存在，MSETNX 命令也会拒绝执行对所有键的设置操作。
     * MSETNX 是一个原子性(atomic)操作， 所有给定键要么就全部都被设置， 要么就全部都不设置。
     *
     * @param entries key-value对集合
     * @return 当所有给定键都设置成功时返回true, 某个给定键已经存在而导致设置未能成功时返回false
     * @since redis.version >= 1.0.1
     */
    public boolean msetnx(Map<String, Object> entries) {
        Boolean b = opsForValue.multiSetIfAbsent(entries);
        return Boolean.TRUE.equals(b);
    }

    /*------------------------------ List ------------------------------*/

    /**
     * 返回列表 key 里索引 index 位置存储的元素。
     *
     * @param key    key
     * @param index  索引
     * @param tClass 返回类型
     * @param <T>    返回类型
     * @return 查询的元素，index 超出索引范围时返回 null
     * @since redis.version >= 1.0.0
     */
    public <T> T lindex(String key, int index, Class<T> tClass) {
        Object o = opsForList.index(key, index);
        return tClass.cast(o);
    }

    /**
     * 把 element 插入到列表 key 中参考值 pivot 的前面
     * 当 key 不存在时，这个list会被看作是空list，什么都不执行。
     * 当 key 存在，值不是列表类型时，返回错误。
     *
     * @param key     key
     * @param pivot   参考值
     * @param element 要插入的元素
     * @return 执行操作后的列表长度，列表中 pivot 参考值不存在的时候返回 -1
     * @since redis.version >= 2.2.0
     */
    public int linsert(String key, Object pivot, Object element) {
        Long size = opsForList.leftPush(key, pivot, element);
        return size == null ? 0 : size.intValue();
    }

    /**
     * 返回存储在 key 中的列表长度。
     * 如果 key 不存在，则 key 被解释为一个空列表，返回 0
     * 如果 key 不是列表类型，返回一个错误。
     *
     * @param key key
     * @return 列表的长度。
     */
    public int llen(String key) {
        Long size = opsForList.size(key);
        return size == null ? 0 : size.intValue();
    }

    /**
     * 原子地从列表 source 中移除并返回第一个或最后一个元素（头或尾取决于 from 参数)，
     * 然后把这个元素插入到列表 destination 的第一个或最后一个元素（头或尾取决于 to 参数)。
     * 如果列表 source 不存在，返回 null ，什么也不执行。
     * 如果列表 source 和destination 相同，相当于从列表的一端删除元素，在放入列表的另一端，所以可以当做一个列表循环命令。（如果 from 和 to 相同，相当于什么也没做。）
     *
     * @param sourceKey      source
     * @param destinationKey destination
     * @param from           from
     * @param to             to
     * @param <T>            返回类型
     * @return 被移除并再次插入的元素。
     * @since redis.version >= 6.2.0
     */
    public <T> T lmove(String sourceKey, String destinationKey, RedisListCommands.Direction from, RedisListCommands.Direction to, Class<T> tClass) {
        Object o = opsForList.move(sourceKey, from, destinationKey, to);
        return tClass.cast(o);
    }

    /**
     * BLMOVE 是 LMOVE 的阻塞版本。
     * 当 source 列表非空时，BLMOVE 的执行效果跟 LMOVE 一样。
     * 当用在事务块 MULTI/EXEC 内，BLMOVE 的执行效果跟 LMOVE 一样。
     * 当列表为空时，Redis 会阻塞客户端连接，直到另一个客户端向 source 列表插入新值或timeout 超时时间到达。
     * timeout 等于 0 时表示永不超时。
     *
     * @param sourceKey      source
     * @param destinationKey destination
     * @param from           from
     * @param to             to
     * @param timeout        超时时间
     * @param tClass         返回类型
     * @param <T>            返回类型
     * @return 从列表source 移除并插入到列表destination的元素。如果 timeout 超时，返回 null
     * @since redis.version >= 6.2.0
     */
    public <T> T blmove(String sourceKey, String destinationKey, RedisListCommands.Direction from, RedisListCommands.Direction to, Duration timeout, Class<T> tClass) {
        Object o = opsForList.move(sourceKey, from, destinationKey, to, timeout);
        return tClass.cast(o);
    }

    /**
     * 删除并返回存储在 key 中的列表的第一个元素
     *
     * @param key    key
     * @param tClass 返回的类型
     * @return 列比中的第一个元素
     * @since redis.version >= 1.0.0
     */
    public <T> T lpop(String key, Class<T> tClass) {
        Object o = opsForList.leftPop(key);
        return tClass.cast(o);
    }

    public <T> List<T> lpop(String key, int count, Class<T> tClass) {
        List<Object> objects = opsForList.leftPop(key, count);
        if (objects == null) {
            return Collections.emptyList();
        }
        return objects.stream().map(tClass::cast).collect(Collectors.toList());
    }

    /**
     * 移出并获取列表的第一个元素，如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。它是 LPOP 的阻塞版本。
     *
     * @param key     key
     * @param timeout 超时时间
     * @param tClass  返回类型
     * @param <T>     返回类型
     * @return 列表的第一个元素的值，如果超时返回 null
     * @since redis.version >= 2.0.0
     */
    public <T> T blpop(String key, Duration timeout, Class<T> tClass) {
        Object o = opsForList.leftPop(key, timeout);
        return tClass.cast(o);
    }

    /**
     * 返回列表 key 中匹配给定 element 成员的索引。
     * 从列表头部开始扫描，直到列表尾部，查找第一个与匹配"element"的成员。
     *
     * @param key   key
     * @param value element
     * @return 返回整数表示匹配元素的位置，返回-1表示没有找到匹配元素。
     */
    public int lpos(String key, Object value) {
        Long pos = opsForList.indexOf(key, value);
        return pos == null ? -1 : pos.intValue();
    }

    /**
     * 将一个或多个值插入到列表 key 的头部
     * 如果 key 不存在，那么在进行 push 操作前会创建一个空列表。
     * 如果 key 存在但不是列表类型，返回一个错误。
     * 元素按在参数中出现的顺序，从左到右依次插入到 list 的头部。
     * 所以对于这个命令例子 lpush mylist a b c，返回的列表是 c 为第一个元素， b 为第二个元素， a 为第三个元素。
     *
     * @param key      key
     * @param elements 元素
     * @return 执行push操作后列表的长度。
     * @since redis.version >= 2.4
     */
    public int lpush(String key, Object... elements) {
        Long len = opsForList.leftPushAll(key, elements);
        return len == null ? 0 : len.intValue();
    }

    public int lpush(String key, Collection<Object> elements) {
        Long len = opsForList.leftPushAll(key, elements);
        return len == null ? 0 : len.intValue();
    }

    /**
     * 当 key 存在并且存储着一个 list 类型值的时候，向值 list 的头部插入 value。
     * 与 LPUSH 不同的是，当 key 不存在的时候不会进行任何操作。
     *
     * @param key     key
     * @param element element
     * @return 执行操作后列表 list 的长度。
     */
    public int lpushx(String key, Object element) {
        Long size = opsForList.leftPushIfPresent(key, element);
        return size == null ? 0 : size.intValue();
    }

    /**
     * <pre>
     * 返回列表中指定区间内的元素，区间以偏移量 START 和 STOP 指定。
     * 其中 0 表示列表的第一个元素， 1 表示列表的第二个元素，以此类推。
     * 也可以使用负数下标，以 -1 表示列表的最后一个元素， -2 表示列表的倒数第二个元素，以此类推。
     *
     * 超出范围的下标值不会引起错误。
     * 如果 start 下标比列表的最大下标 end ( LLEN list 减去 1 )还要大，那么 LRANGE 返回一个空列表。
     * 如果 stop 下标比 end 下标还要大，Redis将 stop 的值设置为 end
     * </pre>
     *
     * @param key key
     * @return 一个列表，包含指定区间 [start, stop] 内的元素。注意是闭区间。
     * @since redis.version >= 1.0.0
     */
    public <T> List<T> lrange(String key, int start, int stop, Class<T> tClass) {
        List<Object> range = opsForList.range(key, start, stop);
        if (range == null) {
            return Collections.emptyList();
        }
        return range.stream().map(tClass::cast).collect(Collectors.toList());
    }

    /**
     * 从列表 key 中删除前 count 个值等于 element 的元素。
     * 不存在key会被当作空list处理，所以当 key 不存在的时候，这个命令会返回 0。
     * <li> count > 0: 从头到尾删除值为 value 的元素。
     * <li> count < 0: 从尾到头删除值为 value 的元素。
     * <li> count = 0: 移除所有值为 value 的元素
     *
     * @param key     key
     * @param count   count
     * @param element element
     * @return 删除元素个数。
     * @since redis.version >= 1.0.0
     */
    public int lrem(String key, int count, Object element) {
        Long remove = opsForList.remove(key, count, element);
        return remove == null ? 0 : remove.intValue();
    }

    /**
     * 设置列表 key 中 index 位置的元素值为 element。
     *
     * @param key     key
     * @param index   index
     * @param element element
     * @since redis.version >= 1.0.0
     */
    public void lset(String key, int index, Object element) {
        opsForList.set(key, index, element);
    }

    /**
     * 修剪(trim)一个已存在的 list，这样 list 就会只包含指定范围的指定元素。
     * start 和 stop 都是由 0开始计数的， 这里的 0 是列表里的第一个元素（表头），1 是第二个元素，以此类推。
     * start 和 end 也可以用负数来表示与表尾的偏移量，比如 -1 表示列表里的最后一个元素， -2 表示倒数第二个，等等。
     * 超过范围的下标并不会产生错误：如果 start 超过列表尾部，或者 start > end，结果会是列表变成空表（即该 key 会被移除）。
     * 如果 end 超过列表尾部，Redis 会将其当作列表的最后一个元素。
     *
     * @param key   key
     * @param start start
     * @param stop  stop
     * @since redis.version >= 1.0.0
     */
    public void ltrim(String key, int start, int stop) {
        opsForList.trim(key, start, stop);
    }

    /**
     * 移除并返回列表 key 的最后一个元素。
     *
     * @param key    key
     * @param tClass 返回类型
     * @param <T>    返回类型
     * @return 最后一个元素的值，key 不存在时返回 null
     * @since redis.version >= 1.0.0
     */
    public <T> T rpop(String key, Class<T> tClass) {
        Object o = opsForList.rightPop(key);
        return tClass.cast(o);
    }

    /**
     * 移除并返回列表 key 的最后 count 个元素。
     *
     * @param key    key
     * @param tClass 返回类型
     * @param <T>    返回类型
     * @return 最后 count 个元素的值，key 不存在时返回 null
     * @since redis.version >= 1.0.0
     */
    public <T> List<T> rpop(String key, int count, Class<T> tClass) {
        List<Object> list = opsForList.rightPop(key, count);
        if (list == null) {
            return Collections.emptyList();
        }
        return list.stream().map(tClass::cast).collect(Collectors.toList());
    }

    /**
     * BRPOP 是 RPOP 的阻塞版本，
     *
     * @param key     key
     * @param timeout 超时时间
     * @param tClass  返回类型
     * @param <T>     返回类型
     * @return 最后一个元素的值，key 不存在时或超时返回 null
     */
    public <T> T brpop(String key, Duration timeout, Class<T> tClass) {
        Object o = opsForList.rightPop(key, timeout);
        return tClass.cast(o);
    }

    /**
     * 在 key 中的列表的尾部插入所有指定的值。
     * 如果 key 不存在，那么会创建一个空的列表然后再进行 push 操作。
     * 当 key 保存的不是列表，那么会返回一个错误。
     *
     * @param key      key
     * @param elements 元素
     * @return 执行 push 操作后的列表长度。
     * @since redis.version >= 2.4 一次可以插入多个元素，老版本一次只能插入一个元素。
     */
    public int rpush(String key, Object... elements) {
        Long size = opsForList.rightPushAll(key, elements);
        return size == null ? 0 : size.intValue();
    }

    public int rpush(String key, Collection<Object> elements) {
        Long size = opsForList.rightPushAll(key, elements);
        return size == null ? 0 : size.intValue();
    }

    /**
     * 将值 value 插入到列表 key 的表尾, 当且仅当 key 存在并且是一个列表。
     * 和 RPUSH 命令不同的是, 当 key 不存在时，RPUSHX 命令什么也不做。
     *
     * @param key     key
     * @param element ellment
     * @return 命令执行之后列表的长度。
     */
    public int rpushx(String key, Object element) {
        Long size = opsForList.rightPushIfPresent(key, element);
        return size == null ? 0 : size.intValue();
    }

    /*------------------------------ Set ------------------------------*/

    /**
     * 将一个或多个成员元素加入到集合中，已经存在于集合的成员元素将被忽略。
     * 假如集合 key 不存在，则创建一个只包含被添加的元素作为成员的集合。
     * 当集合 key 不是集合类型时，返回一个错误。
     *
     * @param key      key
     * @param elements 被添加到集合的元素
     * @return 整数: 返回新成功添加到集合里元素的数量，不包括已经存在于集合中的元素。
     * @since redis.version >= 1.0.0   注意：在 Redis2.4 版本以前， SADD 只接受单个成员值。
     */
    public int sadd(String key, Object... elements) {
        Long num = opsForSet.add(key, elements);
        return num == null ? 0 : num.intValue();
    }

    /**
     * 返回集合中元素的数量
     *
     * @param key key
     * @return 集合中成员的数量。 当集合 key 不存在时，返回 0
     * @since redis >= 1.0.0
     */
    public int scard(String key) {
        Long size = opsForSet.size(key);
        return size == null ? 0 : size.intValue();
    }

    /**
     * <pre>
     * Redis SDIFF 命令返回第一个集合与其他集合之间的差异，也可以认为说第一个集合中独有的元素。不存在的集合 key 将视为空集。
     * 例：
     *   key1 = {a,b,c,d}
     *   key2 = {c}
     *   key3 = {a,c,e}
     *   SDIFF key1 key2 key3 = {b,d}
     * </pre>
     *
     * @param keys   多个集合key
     * @param tClass 返回类型
     * @param <T>    泛型
     * @return 返回第一个集合与其他集合之间的差异
     * @since redis >= 1.0.0
     */
    public <T> Set<T> sdiff(String[] keys, Class<T> tClass) {
        Set<Object> difference = opsForSet.difference(Arrays.asList(keys));
        if (difference == null) {
            return Collections.emptySet();
        }
        return difference.stream().map(tClass::cast).collect(Collectors.toSet());
    }

    /**
     * <pre>
     * SDIFFSTORE 命令的作用和 SDIFF 类似，不同的是它将结果保存到 destination 集合，而把结果集返回给客户端。
     * 例：
     *   key1 = {a,b,c,d}
     *   key2 = {c}
     *   key3 = {a,c,e}
     *   SDIFFSTORE destKey key1 key2 key3 执行后会把 {b,d} 保存到 destKey 中，如果 destKey 已经存在，则将其覆盖
     * </pre>
     *
     * @param keys    多个keys
     * @param destKey 将keys中第一个集合与其他集合之间的差异保存到 destKey 中，如果存在则覆盖
     * @return 整数: 结果集中成员数量。
     * @since redis >= 1.0.0
     */
    public long sdiffstore(String[] keys, String destKey) {
        Long size = opsForSet.differenceAndStore(Arrays.asList(keys), destKey);
        return size == null ? 0 : size;
    }

    /**
     * <pre>
     * SINTER 返回所有给定集合的成员交集。
     * 例如：
     *    key1 = {a,b,c,d}
     *    key2 = {c}
     *    key3 = {a,c,e}
     *    SINTER key1 key2 key3 = {c}
     * </pre>
     *
     * @param keys   多个keys
     * @param tClass 返回类型
     * @param <T>    泛型
     * @return 返回所有给定集合的成员交集
     * @since redis >= 1.0.0
     */
    public <T> Set<T> sinter(String[] keys, Class<T> tClass) {
        Set<Object> set = opsForSet.intersect(Arrays.asList(keys));
        if (set == null) {
            return Collections.emptySet();
        }
        return set.stream().map(tClass::cast).collect(Collectors.toSet());
    }

    /**
     * <pre>
     * SINTERSTORE 命令与 SINTER 命令类似，不同的是它并不是直接返回结果集，而是将结果保存在 destination 集合中。
     * 如果 destination 集合存在, 则会被覆盖。
     * 例如：
     *    key1 = {a,b,c,d}
     *    key2 = {c}
     *    key3 = {a,c,e}
     *    SINTERSTORE destKey key1 key2 key3 执行后会把 {c} 保存到 destKey 中
     * </pre>
     *
     * @param keys    多个keys
     * @param destKey 目标key
     * @return 整数: 结果集中成员数量。
     * @since redis >= 1.0.0
     */
    public long sinterstore(String[] keys, String destKey) {
        Long l = opsForSet.intersectAndStore(Arrays.asList(keys), destKey);
        return l == null ? 0 : l;
    }

    /**
     * <pre>
     * Redis SISMEMBER 用于判断元素 member 是否集合 key 的成员。
     * </pre>
     * @param key key
     * @param member member
     * @return true-如果成员元素是集合的成员 false-如果成员元素不是集合的成员，或 key 不存在
     * @since redis >= 1.0.0
     */
    public boolean sismember(String key, Object member) {
        return Boolean.TRUE.equals(opsForSet.isMember(key, member));
    }

    /**
     * 返回存储在 key 中的集合的所有的成员。 不存在的集合被视为空集合。
     *
     * @param key    key
     * @param tClass 成员类型
     * @param <T>    泛型
     * @return 返回集合中的所有成员。
     * @since redis >= 1.0.0
     */
    public <T> Set<T> smembers(String key, Class<T> tClass) {
        Set<Object> members = opsForSet.members(key);
        if (members == null) {
            return Collections.emptySet();
        }
        return members.stream().map(tClass::cast).collect(Collectors.toSet());
    }

    /**
     *
     * @param key
     * @param member
     */
    public void smismember(String key, Object... member) {
        opsForSet.isMember(key, member);
    }



    /*------------------------------ ZSet ------------------------------*/

    /**
     * <pre>
     * Redis ZADD 命令用于将一个或多个 member 元素及其 score 值加入到有序集 key 当中。
     * 如果某个 member 已经是有序集的成员，那么更新这个 member 的 score 值，并通过重新插入这个 member 元素，来保证该 member 在正确的位置上。
     * 如果有序集合 key 不存在，则创建一个空的有序集并执行 ZADD 操作。
     * 当 key 存在但不是有序集类型时，返回一个错误。
     * score 值可以是整数值或双精度浮点数，score 可为正也可以为负。
     * </pre>
     * @param key
     * @param value
     * @param score
     * @since redis >= 1.2.0
     */
    public void zadd(String key, Object value, double score) {
        opsForZSet.add(key, value, score);
    }

    /**
     *
     * @param key
     * @param scoreMembers key是 member
     * @since redis >= 2.4.0
     */
    public int zadd(String key, Map<Object, Double> scoreMembers) {
        if (scoreMembers == null || scoreMembers.isEmpty()) {
            return 0;
        }
        Set<ZSetOperations.TypedTuple<Object>> set = new HashSet<>();
        scoreMembers.forEach((k, v) -> set.add(new DefaultTypedTuple<>(k, v)));
        Long added = opsForZSet.add(key, set);
        return added == null ? 0 : added.intValue();
    }

    /**
     * Redis ZCARD 命令用于返回有序集的成员个数。
     * @param key key
     * @return 返回有序集的成员个数。当key不存在时，返回 0。
     * @since redis >= 1.2.0
     */
    public int zcard(String key) {
        Long num = opsForZSet.zCard(key);
        if (num == null) {
            throw new RedisException("redis error");
        }
        return num.intValue();
    }

    /**
     * Redis ZCOUNT 命令用于计算有序集合中指定区间的成员数量。
     * @param key key
     * @param min 最小值
     * @param max 最大值
     * @return 返回有序集合中指定区间的成员数量。
     * @since redis >= 2.0.0
     */
    public int zcount(String key, double min, double max) {
        Long count = opsForZSet.count(key, min, max);
        return count == null ? 0 : count.intValue();
    }

    /**
     * <pre>
     * ZINCRBY 为有序集 key 的成员 member 的 score 值加上增量 increment
     * </pre>
     * @param key 有序集 key
     * @param member 成员
     * @param increment 增量
     * @return 返回 member 成员的新 score 值。
     * @since redis >= 1.2.0
     */
    public Double zincrby(String key, Object member, double increment) {
        return opsForZSet.incrementScore(key, member, increment);
    }

    /**
     * <pre>
     * ZINTERSTORE 计算给定的一个或多个有序集的交集，其中给定 key 的个数必须以 numkeys 参数指定，并将该交集(结果集)储存到 destination 。
     * </pre>
     * @param key
     * @param otherKey
     * @param destKey
     * @since redis >= 2.6.0
     */
    public void zinterstore(String key, String otherKey, String destKey) {
        // TODO
        Long l = opsForZSet.intersectAndStore(key, otherKey, destKey);
    }


    /*------------------------------ Server ------------------------------*/

    public void flushdb() {
        redisTemplate.execute((RedisCallback<Object>) connection -> {
            connection.flushDb();
            return "OK";
        });
    }

    // 持续更新......

}
